﻿using System.Reflection.Emit;
using System.Reflection;
using System.Runtime.InteropServices;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace AE.Remoting {
    public sealed class Utilities {
        [ThreadStatic()]
        private static bool p_IsRemoted;
        public static bool IsRemoted {
            get { return p_IsRemoted; }
        }

        private static System.Runtime.Serialization.Formatters.Binary.BinaryFormatter p_Serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        private static object RemoteTcpClient(Delegate func, params object[] @params) {
            SerializableDelegate ctx = new SerializableDelegate(func, @params);
            byte[] data = Serialize(ctx);

            System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(Settings.Current.RemotingHost, Settings.Current.RemotingPort);
            System.Net.Sockets.NetworkStream stream = client.GetStream();

            using (BinaryReader reader = new BinaryReader(stream)) {
                using (BinaryWriter writer = new BinaryWriter(stream)) {
                    writer.Write(data.Length);
                    stream.Write(data, 0, data.Length);

                    int length = reader.ReadInt32();
                    if (length == 0) return null;
                    data = reader.ReadBytes(length);
                }
            }

            SerializableDelegate result = (SerializableDelegate)Deserialize(data);
            if (result.Exception != null) throw result.Exception;

            if (func.Target != null) FastReflection.UpdateFieldsFrom(func.Target, result.ReCreateTarget());

            object returnValue = result.ReturnValue;
            if (returnValue != null && returnValue is Serializable) {
                returnValue = ((Serializable)returnValue).Deserialize();
            }

            return returnValue;
        }

        private static object RemoteWebClient(Delegate func, params object[] @params) {
            SerializableDelegate ctx = new SerializableDelegate(func, @params);
            byte[] data = Serialize(ctx);

            using (WebClient Web = new WebClient()) {
                data = Web.UploadData(Settings.Current.RemotingUrl, data);
            }

            SerializableDelegate result = (SerializableDelegate)Deserialize(data);
            if (result.Exception != null) throw result.Exception;

            if (func.Target != null) FastReflection.UpdateFieldsFrom(func.Target, result.ReCreateTarget());

            object returnValue = result.ReturnValue;
            if (returnValue != null && returnValue is Serializable) {
                returnValue = ((Serializable)returnValue).Deserialize();
            }

            return returnValue;
        }

        private static object RemoteClient(Delegate func, params object[] @params) {
            switch (Settings.Current.RemotingMode) {
                case Settings.RemotingModes.None:
                    return func.DynamicInvoke(@params);
                case Settings.RemotingModes.TcpIp:
                    return RemoteTcpClient(func, @params);
                case Settings.RemotingModes.Web:
                    return RemoteWebClient(func, @params);
                default:
                    throw new NotImplementedException();
            }
        }

        public static void RemoteTcpServer(TcpClient Client) {
            if (!Client.Connected) return;
           NetworkStream stream = Client.GetStream();
            byte[] data;
            int length;

            using (BinaryWriter writer = new BinaryWriter(stream)) {
                using (BinaryReader reader = new BinaryReader(stream)) {
                    if (!Client.Connected) return;
                    length = reader.ReadInt32();
                    if (!Client.Connected) return;
                    data = reader.ReadBytes(length);

                    data = Execute(data);
                    if (!Client.Connected) return;
                    writer.Write(data.Length);
                    if ((data.Length > 0)) {
                        if (!Client.Connected) return;
                        stream.Write(data, 0, data.Length);
                    }
                }
            }
        }

        private static byte[] Serialize(object obj) {
            if (obj == null) return new byte[] { };
            if (!obj.GetType().IsMarkedSerializable()) {
                obj = Serializable.Serialize(obj);
            }
            using (MemoryStream mem = new MemoryStream()) {
                p_Serializer.Serialize(mem, obj);
                return mem.ToArray();
            }
        }

        private static object Deserialize(byte[] data) {
            if (data.Length == 0) return null;
            object obj;
            using (MemoryStream mem = new MemoryStream(data)) {
                obj = p_Serializer.Deserialize(mem);
            }

            if (obj is Serializable) {
                obj = ((Serializable)obj).Deserialize();
            }
            return obj;
        }

        private static T Deserialize<T>(byte[] data) {
            return (T)Deserialize(data);
        }

        static internal byte[] Execute(byte[] data) {
            p_IsRemoted = true;
            SerializableDelegate ctx = Deserialize<SerializableDelegate>(data);
            ctx.Execute();
            return Serialize(ctx);
        }

        public static ReturnType Remote<ReturnType>(Func<ReturnType> func) {
            return (ReturnType)RemoteClient(func, null);
        }

        public static void Remote(Action func) {
            RemoteClient(func, null);
        }

        public static void Remote<T1>(Action<T1> func, T1 Param) {
            RemoteClient(func, new object[] { Param });
        }

        public static void Remote<T1, T2>(Action<T1, T2> func, T1 Param1, T2 Param2) {
            RemoteClient(func, new object[] { Param1, Param2 });
        }

        public static void Remote<T1, T2, T3>(Action<T1, T2, T3> func, T1 Param1, T2 Param2, T3 Param3) {
            RemoteClient(func, new object[] { Param1, Param2, Param3 });
        }

        public static void Remote<T1, T2, T3, T4>(Action<T1, T2, T3, T4> func, T1 Param1, T2 Param2, T3 Param3, T4 Param4) {
            RemoteClient(func, new object[] { Param1, Param2, Param3, Param4 });
        }

        public static ReturnType Remote<T1, ReturnType>(Func<T1, ReturnType> func, T1 Param1) {
            return (ReturnType)RemoteClient(func, new object[] { Param1 });
        }

        public static ReturnType Remote<T1, T2, ReturnType>(Func<T1, T2, ReturnType> func, T1 Param1, T2 Param2) {
            return (ReturnType)RemoteClient(func, new object[] { Param1, Param2 });
        }

        public static ReturnType Remote<T1, T2, T3, ReturnType>(Func<T1, T2, T3, ReturnType> func, T1 Param1, T2 Param2, T3 Param3) {
            return (ReturnType)RemoteClient(func, new object[] { Param1, Param2, Param3 });
        }

        public static ReturnType Remote<T1, T2, T3, T4, ReturnType>(Func<T1, T2, T3, T4, ReturnType> func, T1 Param1, T2 Param2, T3 Param3, T4 Param4) {
            return (ReturnType)RemoteClient(func, new object[] { Param1, Param2, Param3, Param4 });
        }
    }
}
 